package com.cloudlead.utils.scheduler.impl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.annotation.Resource;
import javax.sql.DataSource;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathException;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.DailyTimeIntervalScheduleBuilder;
import org.quartz.DailyTimeIntervalTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.JobListener;
import org.quartz.Matcher;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerListener;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TimeOfDay;
import org.quartz.Trigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.TriggerListener;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.matchers.KeyMatcher;
import org.quartz.simpl.LoadingLoaderClassLoadHelper;
import org.quartz.xml.ValidationException;
import org.quartz.xml.XMLSchedulingDataProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.xml.sax.SAXException;

import com.cloudlead.common.lang.utils.ObjectId;
import com.cloudlead.utils.scheduler.JobMetadata;
import com.cloudlead.utils.scheduler.ScheduledTaskService;
import com.cloudlead.utils.scheduler.exception.ScheduledTaskException;
import com.cloudlead.utils.scheduler.util.ScheduleConnectionProvider;

@Service
public class ScheduledTaskServiceImpl implements ScheduledTaskService {

	private static final String JOB_DATA_SOURCE = "dataSource";

	private static final Logger LOGGER = LoggerFactory
			.getLogger(ScheduledTaskService.class);

	@Resource
	ApplicationContext ctx;

	@Resource(name = "quartzConfig")
	Properties quartzConfig;

	@Resource(name = "quartzDatasourceConfig")
	Properties quartzDatasourceConfig;

	private Scheduler quartzScheduler;

	@Override
	public void start() {
		try {
			String jobStore = quartzConfig
					.getProperty("org.quartz.jobStore.class");
			if (null == jobStore || "".equals(jobStore.trim())) {
				DataSource dataSource = ctx.getBean(JOB_DATA_SOURCE,DataSource.class);
				if (null != dataSource) {
					ScheduleConnectionProvider.setDataSource(dataSource);
					quartzConfig.putAll(quartzDatasourceConfig);
				}
			}

			StdSchedulerFactory schedulerFactory = new StdSchedulerFactory(
					quartzConfig);
			quartzScheduler = schedulerFactory.getScheduler();
			quartzScheduler.getContext().put(SPRING_CONTEXT_KEY, ctx);
			quartzScheduler.start();
			LOGGER.debug("Start schedule service.");
		} catch (SchedulerException e) {
			e.printStackTrace();
			quartzScheduler = null;
			throw new ScheduledTaskException("Start schedule service error.", e);
		}
	}

	@Override
	public void stop() {
		if (null != quartzScheduler) {
			try {
				quartzScheduler.pauseAll();
				quartzScheduler.shutdown();
				quartzScheduler = null;
				LOGGER.debug("Stop schedule service.");
			} catch (SchedulerException e) {
				e.printStackTrace();
				quartzScheduler = null;
				throw new ScheduledTaskException(
						"Stop schedule service error.", e);
			}
		}
	}

	@Override
	public JobKey addJob(Class<? extends Job> jobClass, String name,
			String group, JobDataMap dataMap, TimeOfDay timeOfDay) {
		return addJob(jobClass, name, group, dataMap, timeOfDay, 0);
	}

	@Override
	public JobKey addJob(Class<? extends Job> jobClass, String name,
			String group, JobDataMap dataMap, TimeOfDay timeOfDay, long period) {
		if (null == name || name.length() == 0) {
			name = ObjectId.get();
		}
		if (null == group || "".equals(group)) {
			group = Scheduler.DEFAULT_GROUP;
		}
		JobDetail detail = buildJobDetail(jobClass, dataMap, name, group);
		DailyTimeIntervalScheduleBuilder scheduleBuilder = DailyTimeIntervalScheduleBuilder
				.dailyTimeIntervalSchedule();
		if (null != timeOfDay) {
			scheduleBuilder.startingDailyAt(timeOfDay);
		}
		if (period > 0) {
			scheduleBuilder.withIntervalInSeconds((int) period);
		}
		TriggerBuilder<DailyTimeIntervalTrigger> triggerBuilder = TriggerBuilder
				.newTrigger().withIdentity(name, group)
				.withSchedule(scheduleBuilder).forJob(name, group);
		DailyTimeIntervalTrigger trigger = triggerBuilder.build();
		return addJob(detail, trigger);
	}

	@Override
	public JobKey addJob(Class<? extends Job> jobClass, String name,
			String group, JobDataMap dataMap, String cron) {
		return addJob(jobClass, name, group, dataMap, cron, null, null);
	}

	@Override
	public JobKey addJob(Class<? extends Job> jobClass, String name,
			String group, JobDataMap dataMap, String cron, Date startTime,
			Date endTime) {
		if (null == name || name.length() == 0) {
			name = ObjectId.get();
		}
		if (null == group || "".equals(group)) {
			group = Scheduler.DEFAULT_GROUP;
		}
		JobDetail detail = buildJobDetail(jobClass, dataMap, name, group);
		Trigger trigger;
		if (null != cron && cron.length() > 0) {
			TriggerBuilder<CronTrigger> triggerBuilder = TriggerBuilder
					.newTrigger().withIdentity(name, group)
					.withSchedule(CronScheduleBuilder.cronSchedule(cron))
					.forJob(name, group);
			if (null != startTime) {
				triggerBuilder.startAt(startTime);
			}
			if (null != endTime) {
				triggerBuilder.endAt(endTime);
			}
			trigger = triggerBuilder.build();

		} else {
			TriggerBuilder<SimpleTrigger> triggerBuilder = TriggerBuilder
					.newTrigger().withIdentity(name, group)
					.withSchedule(SimpleScheduleBuilder.simpleSchedule())
					.forJob(name, group);
			if (null != startTime) {
				triggerBuilder.startAt(startTime);
			}
			if (null != endTime) {
				triggerBuilder.endAt(endTime);
			}
			trigger = triggerBuilder.build();
		}
		return addJob(detail, trigger);
	}

	private JobDetail buildJobDetail(Class<? extends Job> jobClass,
			JobDataMap dataMap, String name, String group) {
		JobBuilder jobBuilder = JobBuilder.newJob(jobClass).withIdentity(name,
				group);
		if (null != dataMap) {
			jobBuilder.usingJobData(dataMap);
		}
		JobDetail detail = jobBuilder.build();
		return detail;
	}

	@Override
	public JobKey addJob(JobDetail jobDetail, Trigger trigger) {
		try {
			quartzScheduler.scheduleJob(jobDetail, trigger);
			LOGGER.debug("Add schedule job [" + jobDetail.getKey() + "] .");
			return jobDetail.getKey();
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Add schedule job error.", e);
		}
	}

	@Override
	public void addJob(String xml, String systemId) {
		InputStream is = null;
		try {
			LoadingLoaderClassLoadHelper classLoadHelper = new LoadingLoaderClassLoadHelper();
			classLoadHelper.initialize();
			XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(
					classLoadHelper);
			is = new ByteArrayInputStream(xml.getBytes());
			processor.processStreamAndScheduleJobs(is, systemId,
					quartzScheduler);
		} catch (ParserConfigurationException | ClassNotFoundException
				| ValidationException | SAXException | XPathException
				| IOException | SchedulerException | ParseException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Add schedule job error.", e);
		} finally {
			if (null != is) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	@Override
	public void pauseJob(String name, String group) {
		try {
			quartzScheduler.pauseTrigger(new TriggerKey(name, group));
			quartzScheduler.pauseJob(new JobKey(name, group));
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Pause schedule job error.", e);
		}
	}

	@Override
	public void resumeJob(String name, String group) {
		try {
			quartzScheduler.resumeTrigger(new TriggerKey(name, group));
			quartzScheduler.resumeJob(new JobKey(name, group));
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Resume schedule job error.", e);
		}
	}

	@Override
	public void deleteJob(String name, String group) {
		try {
			quartzScheduler.unscheduleJob(new TriggerKey(name, group));
			quartzScheduler.deleteJob(new JobKey(name, group));
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Delete schedule job error.", e);
		}

	}

	@Override
	public void pauseGroup(String group) {
		try {
			quartzScheduler.pauseTriggers(GroupMatcher
					.triggerGroupEquals(group));
			quartzScheduler.pauseJobs(GroupMatcher.jobGroupEquals(group));
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Pause schedule job group error.",
					e);
		}

	}

	@Override
	public void resumeGroup(String group) {
		try {
			quartzScheduler.resumeTriggers(GroupMatcher
					.triggerGroupEquals(group));
			quartzScheduler.resumeJobs(GroupMatcher.jobGroupEquals(group));
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException(
					"Resume schedule job group error.", e);
		}

	}

	@Override
	public void deleteGroup(String group) {
		try {
			Set<TriggerKey> triggerKeySet = quartzScheduler
					.getTriggerKeys(GroupMatcher.triggerGroupEquals(group));
			List<TriggerKey> triggerKeys = new ArrayList<>();
			triggerKeys.addAll(triggerKeySet);
			quartzScheduler.unscheduleJobs(triggerKeys);

			Set<JobKey> jobKeySet = quartzScheduler.getJobKeys(GroupMatcher
					.jobGroupEquals(group));
			List<JobKey> jobKeys = new ArrayList<>();
			jobKeys.addAll(jobKeySet);
			quartzScheduler.deleteJobs(jobKeys);
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException(
					"Delete schedule job group error.", e);
		}

	}

	@Override
	public void pauseAll() {
		try {
			quartzScheduler.pauseAll();
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Pause all schedule job error.", e);
		}

	}

	@Override
	public void resumeAll() {
		try {
			quartzScheduler.resumeAll();
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Resume all schedule job error.",
					e);
		}

	}

	@Override
	public void deleteAll() {
		try {
			Set<TriggerKey> triggerKeySet = quartzScheduler
					.getTriggerKeys(GroupMatcher.anyTriggerGroup());
			List<TriggerKey> triggerKeys = new ArrayList<>();
			triggerKeys.addAll(triggerKeySet);
			quartzScheduler.unscheduleJobs(triggerKeys);

			Set<JobKey> jobKeySet = quartzScheduler.getJobKeys(GroupMatcher
					.anyJobGroup());
			List<JobKey> jobKeys = new ArrayList<>();
			jobKeys.addAll(jobKeySet);
			quartzScheduler.deleteJobs(jobKeys);
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Delete all schedule job error.",
					e);
		}
	}

	@Override
	public Set<JobMetadata> listJobMetadata(String group, String jobClassName,
			TriggerState triggerState) {
		try {
			Set<JobMetadata> jobMetadatas = new HashSet<>();
			List<String> jobGroupNames = new ArrayList<>();
			if (null != group && !"".equals(group)) {
				jobGroupNames.add(group);
			} else {
				jobGroupNames = quartzScheduler.getJobGroupNames();
			}
			for (String groupName : jobGroupNames) {
				Set<JobKey> jobKeys = quartzScheduler.getJobKeys(GroupMatcher
						.jobGroupEquals(groupName));
				for (JobKey jobKey : jobKeys) {

					JobDetail jobDetail = quartzScheduler.getJobDetail(jobKey);
					JobMetadata metadata = buildJobMetadata(jobDetail);
					if (null != jobClassName
							&& !jobClassName.equals(metadata.getJobClassName())) {
						continue;
					}
					if (null != triggerState
							&& !triggerState.equals(metadata.getTriggerState())) {
						continue;
					}
					jobMetadatas.add(metadata);
				}
			}
			return jobMetadatas;
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("List all schedule job error.", e);
		}
	}

	private JobMetadata buildJobMetadata(JobDetail jobDetail)
			throws SchedulerException {
		JobMetadata metadata = new JobMetadata();

		metadata.setSchedName(quartzScheduler.getSchedulerName());
		metadata.setName(jobDetail.getKey().getName());
		metadata.setGroup(jobDetail.getKey().getGroup());
		String jobClassName_ = jobDetail.getJobClass().getName();
		metadata.setJobClassName(jobClassName_);
		metadata.setDescription(jobDetail.getDescription());

		TriggerKey triggerKey = new TriggerKey(jobDetail.getKey().getName(),
				jobDetail.getKey().getGroup());
		TriggerState triggerState_ = quartzScheduler
				.getTriggerState(triggerKey);
		metadata.setTriggerState(triggerState_);
		Trigger trigger = quartzScheduler.getTrigger(triggerKey);
		if (trigger == null || !(trigger instanceof CronTrigger)) {
			metadata.setCron(null);
		} else {
			metadata.setCron(((CronTrigger) trigger).getCronExpression());
		}
		return metadata;
	}

	@Override
	public void addJobListener(JobListener jobListener, JobKey... jobKeys) {
		List<Matcher<JobKey>> matchers = null;
		try {
			if (null != jobKeys && jobKeys.length > 0) {
				matchers = new ArrayList<>();
				for (int i = 0; i < jobKeys.length; i++) {
					KeyMatcher<JobKey> keyMatcher = KeyMatcher
							.keyEquals(jobKeys[i]);
					matchers.add(keyMatcher);
				}
			}
			quartzScheduler.getListenerManager().addJobListener(jobListener,
					matchers);
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException(
					"Add schedule job listener error.", e);
		}

	}

	@Override
	public void addTriggerListener(TriggerListener triggerListener,
			JobKey... jobKeys) {
		List<Matcher<TriggerKey>> matchers = null;
		try {
			if (null != jobKeys && jobKeys.length > 0) {
				matchers = new ArrayList<>();
				for (int i = 0; i < jobKeys.length; i++) {
					KeyMatcher<TriggerKey> keyMatcher = KeyMatcher
							.keyEquals(new TriggerKey(jobKeys[i].getName(),
									jobKeys[i].getGroup()));
					matchers.add(keyMatcher);
				}
			}
			quartzScheduler.getListenerManager().addTriggerListener(
					triggerListener, matchers);
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException(
					"Add schedule trigger listener error.", e);
		}
	}

	@Override
	public void addSchedulerListener(SchedulerListener schedulerListener) {
		try {
			quartzScheduler.getListenerManager().addSchedulerListener(
					schedulerListener);
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Add schedule listener error.", e);
		}

	}

	@Override
	public void removeJobListener(String jobListener) {
		try {
			quartzScheduler.getListenerManager().removeJobListener(jobListener);
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException(
					"Remove schedule job listener error.", e);
		}

	}

	@Override
	public void removeTriggerListener(String triggerListener) {
		try {
			quartzScheduler.getListenerManager().removeTriggerListener(
					triggerListener);
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException(
					"Remove schedule trigger listener error.", e);
		}

	}

	@Override
	public void removeSchedulerListener(SchedulerListener schedulerListener) {
		try {
			quartzScheduler.getListenerManager().removeSchedulerListener(
					schedulerListener);
		} catch (SchedulerException e) {
			e.printStackTrace();
			throw new ScheduledTaskException("Remove schedule listener error.",
					e);
		}

	}

	@Override
	public Scheduler getQuartzScheduler() {
		return quartzScheduler;
	}

}
